﻿using Docx2Epub.Docx;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;

namespace Docx2Epub.Epub
{
    class EpubNavigationWriter : WriterBase
    {
        private string ncxFilePath;
        private IEnumerable<DocElement> elements;
        private EPubMetadatas metaDatas;
        private int playOrder;

        public EpubNavigationWriter(string outputPath, IEnumerable<DocElement> elements, EPubMetadatas metadatas)
        {
            this.ncxFilePath = Path.Combine(outputPath, "OEBPS", "toc.ncx");
            this.elements = elements;
            this.metaDatas = metadatas;
        }

        internal void WriteOut()
        {
            playOrder = 1;
            XElement doc = XElement.Load(ncxFilePath);
            // create header
            // set uid
            doc.XPathSelectElement("/ncx:head/ncx:meta[@name='dtb:uid']", nsmgr).Attribute("content").Value = metaDatas.BookId;

            // depth required ?

            // set title
            doc.XPathSelectElement("/ncx:docTitle/ncx:text", nsmgr).Value = metaDatas.Title;

            // create navmap
            var navmap = doc.XPathSelectElement("/ncx:navMap", nsmgr);
            var headings = new LinkedList<DocHeading>(this.elements.Where(e => e as DocHeading != null).Select(e => (DocHeading)e));

            foreach (var c in getChapterNavPoints(headings))
                navmap.Add(c);

            using (FileStream fs = new FileStream(ncxFilePath, FileMode.Create, FileAccess.Write))
            using (StreamWriter sr = new StreamWriter(fs))
                doc.Save(sr);
        }

        private IEnumerable<XElement> getChapterNavPoints(LinkedList<DocHeading> headings)
        {
            DocChapter currentChapter;
            while (headings.Count != 0 && (currentChapter = headings.First.Value as DocChapter) != null)
            {
                headings.RemoveFirst();
                var chapElement = makeChapterElement(currentChapter);
                addSections(headings, currentChapter, chapElement);
                yield return chapElement;
            }
        }

        private void addSections(LinkedList<DocHeading> headings, DocChapter currentChapter, XElement chapElement)
        {
            DocSection currentSection;
            XElement parentElement = chapElement;

            // section loop
            while (headings.Count != 0 && (currentSection = headings.First.Value as DocSection) != null)
            {
                headings.RemoveFirst();
                var currentElement = makeSectionNavPoint(currentSection, currentChapter);
                parentElement.Add(currentElement);

                DocSection nextSection;
                if (headings.First == null) // reach to end
                    break;
                if ((nextSection = headings.First.Value as DocSection) == null) // reach to new chapter
                    break;

                if (nextSection.Level > currentSection.Level)
                    parentElement = currentElement;
                else if (nextSection.Level < currentSection.Level)
                    parentElement = parentElement.Parent;
            }
        }

        private XElement makeChapterElement(DocChapter currentChapter)
        {
            var chapElement = new XElement(ncx + "navPoint",
                new XAttribute("class", "h1"),
                new XAttribute("id", "heading" + currentChapter.OrderNumber),
                new XAttribute("playOrder", this.playOrder++),
                new XElement(ncx + "navLabel", new XElement(ncx + "text", currentChapter.Title)),
                new XElement(ncx + "content", new XAttribute("src", currentChapter.FileName)));
            return chapElement;
        }

        private XElement makeSectionNavPoint(DocSection currentSection, DocChapter currentChapter)
        {
            var navPoint = new XElement(ncx + "navPoint",
                new XAttribute("class", "h" + currentSection.Level),
                new XAttribute("id", "heading" + currentSection.OrderNumber),
                new XAttribute("playOrder", this.playOrder++),
                new XElement(ncx + "navLabel", new XElement(ncx + "text", currentSection.Title)),
                new XElement(ncx + "content", new XAttribute("src", currentChapter.FileName + "#" + currentSection.SectionNavigationId)));
            return navPoint;
        }
    }
}
